package com.king.common.utils.file;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.king.framework.config.ProjectConfig;
import com.king.common.utils.DateTimeUtil;
import com.king.common.utils.ServletUtils;
import com.king.common.utils.text.StringUtils;
import com.king.common.utils.uuid.IdUtils;

import org.apache.commons.io.FilenameUtils;

/**
 * 文件处理工具类
 * 
 * @author king
 */
public class FileUtils {
	

	private static Logger log = LoggerFactory.getLogger(FileUtils.class);
	

	public static final String CONTENT_TYPE = "application/octet-stream; charset=utf-8";

	public static final String CONTENT_LENGTH = "Content-Length";
	
	public static final String CONTENT_DISPOSITION = "Content-Disposition";
	
	public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";

	/**
	 * 输出指定文件的byte数组
	 * 
	 * @param filePath
	 *            文件路径
	 * @param os
	 *            输出流
	 * @return
	 */
	public static void writeBytes(String filePath, OutputStream os) throws IOException {
		FileInputStream fis = null;
		try {
			File file = new File(filePath);
			if (!file.exists()) {
				throw new FileNotFoundException(filePath);
			}
			fis = new FileInputStream(file);
			byte[] b = new byte[1024];
			int length;
			while ((length = fis.read(b)) > 0) {
				os.write(b, 0, length);
			}
		} catch (IOException e) {
			throw e;
		} finally {
			IOUtils.close(os);
			IOUtils.close(fis);
		}
	}

	/**
	 * 写数据到文件中
	 *
	 * @param data
	 *            数据
	 * @return 目标文件
	 * @throws IOException
	 *             IO异常
	 */
	public static String writeImportBytes(byte[] data) throws IOException {
		return writeBytes(data, ProjectConfig.getImportPath());
	}

	/**
	 * 写数据到文件中
	 *
	 * @param data
	 *            数据
	 * @param uploadDir
	 *            目标文件
	 * @return 目标文件
	 * @throws IOException
	 *             IO异常
	 */
	public static String writeBytes(byte[] data, String uploadDir) throws IOException {
		FileOutputStream fos = null;
		String pathName = "";
		try {
			String extension = getFileExtendName(data);
			pathName = DateTimeUtil.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
			File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
			fos = new FileOutputStream(file);
			fos.write(data);
		} finally {
			IOUtils.close(fos);
		}
		return FileUploadUtils.getPathFileName(uploadDir, pathName);
	}

	/**
	 * 删除文件
	 * 
	 * @param filePath
	 *            文件
	 * @return
	 */
	public static boolean deleteFile(String filePath) {
		boolean flag = false;
		File file = new File(filePath);
		// 路径为文件且不为空则进行删除
		if (file.isFile() && file.exists()) {
			flag = file.delete();
		}
		return flag;
	}

	/**
	 * 文件名称验证
	 * 
	 * @param filename
	 *            文件名称
	 * @return true 正常 false 非法
	 */
	public static boolean isValidFilename(String filename) {
		return filename.matches(FILENAME_PATTERN);
	}

	/**
	 * 检查文件是否可下载
	 * 
	 * @param resource
	 *            需要下载的文件
	 * @return true 正常 false 非法
	 */
	public static boolean checkAllowDownload(String resource) {
		// 禁止目录上跳级别
		if (StringUtils.contains(resource, "..")) {
			return false;
		}

		// 检查允许下载的文件规则
		if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) {
			return true;
		}

		// 不在允许下载的文件规则
		return false;
	}

	/**
	 * 下载文件名重新编码
	 * 
	 * @param request
	 *            请求对象
	 * @param fileName
	 *            文件名
	 * @return 编码后的文件名
	 */
	public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
			throws UnsupportedEncodingException {
		final String agent = request.getHeader("USER-AGENT");
		String filename = fileName;
		if (agent.contains("MSIE")) {
			// IE浏览器
			filename = URLEncoder.encode(filename, "utf-8");
			filename = filename.replace("+", " ");
		} else if (agent.contains("Firefox")) {
			// 火狐浏览器
			filename = new String(fileName.getBytes(), "ISO8859-1");
		} else if (agent.contains("Chrome")) {
			// google浏览器
			filename = URLEncoder.encode(filename, "utf-8");
		} else {
			// 其它浏览器
			filename = URLEncoder.encode(filename, "utf-8");
		}
		return filename;
	}

	/**
	 * 下载文件名重新编码
	 *
	 * @param response
	 *            响应对象
	 * @param realFileName
	 *            真实文件名
	 */
	public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName)
			throws UnsupportedEncodingException {
		String percentEncodedFileName = percentEncode(realFileName);

		StringBuilder contentDispositionValue = new StringBuilder();
		contentDispositionValue.append("attachment; filename=").append(percentEncodedFileName).append(";")
				.append("filename*=").append("utf-8''").append(percentEncodedFileName);

		response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
		response.setHeader("Content-disposition", contentDispositionValue.toString());
		response.setHeader("download-filename", percentEncodedFileName);
	}

	/**
	 * 百分号编码工具方法
	 *
	 * @param s
	 *            需要百分号编码的字符串
	 * @return 百分号编码后的字符串
	 */
	public static String percentEncode(String s) throws UnsupportedEncodingException {
		String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
		return encode.replaceAll("\\+", "%20");
	}

	/**
	 * 获取图像后缀
	 * 
	 * @param photoByte
	 *            图像数据
	 * @return 后缀名
	 */
	public static String getFileExtendName(byte[] photoByte) {
		String strFileExtendName = "jpg";
		if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
				&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) {
			strFileExtendName = "gif";
		} else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) {
			strFileExtendName = "jpg";
		} else if ((photoByte[0] == 66) && (photoByte[1] == 77)) {
			strFileExtendName = "bmp";
		} else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) {
			strFileExtendName = "png";
		}
		return strFileExtendName;
	}

	/**
	 * 获取文件名称 /profile/upload/2022/04/16/king.png -- king.png
	 * 
	 * @param fileName
	 *            路径名称
	 * @return 没有文件路径的名称
	 */
	public static String getName(String fileName) {
		if (fileName == null) {
			return null;
		}
		int lastUnixPos = fileName.lastIndexOf('/');
		int lastWindowsPos = fileName.lastIndexOf('\\');
		int index = Math.max(lastUnixPos, lastWindowsPos);
		return fileName.substring(index + 1);
	}

	/**
	 * 获取不带后缀文件名称 /profile/upload/2022/04/16/king.png -- king
	 * 
	 * @param fileName
	 *            路径名称
	 * @return 没有文件路径和后缀的名称
	 */
	public static String getNameNotSuffix(String fileName) {
		if (fileName == null) {
			return null;
		}
		String baseName = FilenameUtils.getBaseName(fileName);
		return baseName;
	}

	/**
	 * 压缩文件或目录
	 * 
	 * @param srcDirName
	 *            压缩的根目录
	 * @param fileName
	 *            根目录下的待压缩的文件名或文件夹名，其中*或""表示跟目录下的全部文件
	 * @param descFileName
	 *            目标zip文件
	 */
	public static void zipFiles(String srcDirName, String fileName, String descFileName) {
		// 判断目录是否存在
		if (srcDirName == null) {
			log.debug("文件压缩失败，目录 " + srcDirName + " 不存在!");
			return;
		}
		File fileDir = new File(srcDirName);
		if (!fileDir.exists() || !fileDir.isDirectory()) {
			log.debug("文件压缩失败，目录 " + srcDirName + " 不存在!");
			return;
		}
		String dirPath = fileDir.getAbsolutePath();
		File descFile = new File(descFileName);
		try {
			ZipOutputStream zouts = new ZipOutputStream(new FileOutputStream(descFile));
			if ("*".equals(fileName) || "".equals(fileName)) {
				FileUtils.zipDirectoryToZipFile(dirPath, fileDir, zouts);
			} else {
				File file = new File(fileDir, fileName);
				if (file.isFile()) {
					FileUtils.zipFilesToZipFile(dirPath, file, zouts);
				} else {
					FileUtils.zipDirectoryToZipFile(dirPath, file, zouts);
				}
			}
			zouts.close();
			log.debug(descFileName + " 文件压缩成功!");
		} catch (Exception e) {
			log.debug("文件压缩失败：" + e.getMessage());
			e.printStackTrace();
		}

	}

	/**
	 * 解压缩ZIP文件，将ZIP文件里的内容解压到descFileName目录下
	 * 
	 * @param zipFileName
	 *            需要解压的ZIP文件
	 * @param descFileName
	 *            目标文件
	 */
	public static boolean unZipFiles(String zipFileName, String descFileName) {
		String descFileNames = descFileName;
		if (!descFileNames.endsWith(File.separator)) {
			descFileNames = descFileNames + File.separator;
		}
		try {
			// 根据ZIP文件创建ZipFile对象
			org.apache.tools.zip.ZipFile zipFile = new ZipFile(zipFileName);
			ZipEntry entry = null;
			String entryName = null;
			String descFileDir = null;
			byte[] buf = new byte[4096];
			int readByte = 0;
			// 获取ZIP文件里所有的entry
			@SuppressWarnings("rawtypes")
			Enumeration enums = zipFile.getEntries();
			// 遍历所有entry
			while (enums.hasMoreElements()) {
				entry = (ZipEntry) enums.nextElement();
				// 获得entry的名字
				entryName = entry.getName();
				descFileDir = descFileNames + entryName;
				if (entry.isDirectory()) {
					// 如果entry是一个目录，则创建目录
					new File(descFileDir).mkdirs();
					continue;
				} else {
					// 如果entry是一个文件，则创建父目录
					new File(descFileDir).getParentFile().mkdirs();
				}
				File file = new File(descFileDir);
				// 打开文件输出流
				OutputStream os = new FileOutputStream(file);
				// 从ZipFile对象中打开entry的输入流
				InputStream is = zipFile.getInputStream(entry);
				while ((readByte = is.read(buf)) != -1) {
					os.write(buf, 0, readByte);
				}
				os.close();
				is.close();
			}
			zipFile.close();
			log.debug("文件解压成功!");
			return true;
		} catch (Exception e) {
			log.debug("文件解压失败：" + e.getMessage());
			return false;
		}
	}

	/**
	 * 将目录压缩到ZIP输出流
	 * 
	 * @param dirPath
	 *            目录路径
	 * @param fileDir
	 *            文件信息
	 * @param zouts
	 *            输出流
	 */
	public static void zipDirectoryToZipFile(String dirPath, File fileDir, ZipOutputStream zouts) {
		if (fileDir.isDirectory()) {
			File[] files = fileDir.listFiles();
			// 空的文件夹
			if (files.length == 0) {
				// 目录信息
				ZipEntry entry = new ZipEntry(getEntryName(dirPath, fileDir));
				try {
					zouts.putNextEntry(entry);
					zouts.closeEntry();
				} catch (Exception e) {
					e.printStackTrace();
				}
				return;
			}

			for (int i = 0; i < files.length; i++) {
				if (files[i].isFile()) {
					// 如果是文件，则调用文件压缩方法
					FileUtils.zipFilesToZipFile(dirPath, files[i], zouts);
				} else {
					// 如果是目录，则递归调用
					FileUtils.zipDirectoryToZipFile(dirPath, files[i], zouts);
				}
			}

		}

	}

	/**
	 * 将文件压缩到ZIP输出流
	 * 
	 * @param dirPath
	 *            目录路径
	 * @param file
	 *            文件
	 * @param zouts
	 *            输出流
	 */
	public static void zipFilesToZipFile(String dirPath, File file, ZipOutputStream zouts) {
		FileInputStream fin = null;
		ZipEntry entry = null;
		// 创建复制缓冲区
		byte[] buf = new byte[4096];
		int readByte = 0;
		if (file.isFile()) {
			try {
				// 创建一个文件输入流
				fin = new FileInputStream(file);
				// 创建一个ZipEntry
				entry = new ZipEntry(getEntryName(dirPath, file));
				// 存储信息到压缩文件
				zouts.putNextEntry(entry);
				// 复制字节到压缩文件
				while ((readByte = fin.read(buf)) != -1) {
					zouts.write(buf, 0, readByte);
				}
				zouts.closeEntry();
				fin.close();
				System.out.println("添加文件 " + file.getAbsolutePath() + " 到zip文件中!");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 * 获取待压缩文件在ZIP文件中entry的名字，即相对于跟目录的相对路径名
	 * 
	 * @param dirPath
	 *            目录名
	 * @param file
	 *            entry文件名
	 * @return
	 */
	private static String getEntryName(String dirPath, File file) {
		String dirPaths = dirPath;
		if (!dirPaths.endsWith(File.separator)) {
			dirPaths = dirPaths + File.separator;
		}
		String filePath = file.getAbsolutePath();
		// 对于目录，必须在entry名字后面加上"/"，表示它将以目录项存储
		if (file.isDirectory()) {
			filePath += "/";
		}
		int index = filePath.indexOf(dirPaths);

		return filePath.substring(index + dirPaths.length());
	}

	/**
	 * 获取绝对路径
	 * 
	 * @param urlPath
	 * @return
	 */
	public static String getAbsolutePath(String urlPath) {
		if (StringUtils.isBlank(urlPath)) {
			return "";
		} else {
			return "d:/rap/" + urlPath.substring(urlPath.indexOf("/", 1), urlPath.length());
		}
	}

	
	
	/**
	 * 创建单个文件
	 * 
	 * @param descFileName
	 *            文件名，包含路径
	 * @return 如果创建成功，则返回true，否则返回false
	 */
	public static boolean createFile(String descFileName) {
		File file = new File(descFileName);
		if (file.exists()) {
			log.debug("文件 " + descFileName + " 已存在!");
			return false;
		}
		if (descFileName.endsWith(File.separator)) {
			log.debug(descFileName + " 为目录，不能创建目录!");
			return false;
		}
		if (!file.getParentFile().exists()) {
			// 如果文件所在的目录不存在，则创建目录
			if (!file.getParentFile().mkdirs()) {
				log.debug("创建文件所在的目录失败!");
				return false;
			}
		}

		// 创建文件
		try {
			if (file.createNewFile()) {
				log.debug(descFileName + " 文件创建成功!");
				return true;
			} else {
				log.debug(descFileName + " 文件创建失败!");
				return false;
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.debug(descFileName + " 文件创建失败!");
			return false;
		}

	}
	
	
	/**
	 * 将内容写入文件
	 * 
	 * @param content
	 * @param filePath
	 */
	public static void writeFile(String content, String filePath) {
		if (content == null) {
			content = "";
		}
		try {
			if (FileUtils.createFile(filePath)) {
				FileWriter fileWriter = new FileWriter(filePath, true);
				BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
				bufferedWriter.write(content);
				bufferedWriter.close();
				fileWriter.close();
			} else {
				log.info("生成失败，文件已存在！");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void writeFileToResponse(File file, HttpServletResponse response, String newFileName,
			HttpServletRequest request) throws IOException {
//		response.setContentType(CONTENT_TYPE);
		response.setHeader("Content-Type","application/octet-stream");
		response.setHeader(CONTENT_LENGTH, String.valueOf(file.length()));
		String explorerType = ServletUtils.getExplorerType(request);
		if (explorerType != null && explorerType.contains("IE")) {
			// IE
			response.setHeader(CONTENT_DISPOSITION,
					"attachment; filename=\"" + ServletUtils.encode(newFileName, "UTF-8") + "\"");
		} else {
			// fireFox/Chrome
			response.setHeader(CONTENT_DISPOSITION, "attachment; filename="
					+ new String(newFileName.getBytes("utf-8"), "ISO8859-1"));
		}
		setOutputStream(response, file);
	}

	public static void setOutputStream(final HttpServletResponse response, final File file) throws IOException {
		BufferedOutputStream output = null;
		BufferedInputStream input = null;

		try {
			input = new BufferedInputStream(new FileInputStream(file));
			output = new BufferedOutputStream(response.getOutputStream());
//			byte[] buff = new byte[2048];
//			int readLen = 0;
//			while ((readLen = input.read(buff, 0, buff.length)) != -1) {
//				output.write(buff, 0, readLen);
//			}
//			output.flush();
			
			
			//缓冲流，每次读取1024
		    byte[] buff = new byte[1024];
		    int readLength = 0;
		     while (( readLength = input.read(buff)) != -1) {
		         //每次写入缓冲流buff读到的字节长度，而不是buff.length
		    	 output.write(buff, 0, readLength);
		     }
		     output.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
			throw e;
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					input = null;
				}
			}
			if (output != null) {
				try {
					output.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					output = null;
				}
			}
		}
	}
	
	
	
	
	
	
	public static void downFile(File file, HttpServletResponse response, String filename){
        FileInputStream is = null;
        BufferedInputStream bs = null;
        OutputStream os = null;
        try {
            //设置Headers
            response.setHeader("Content-Type","application/octet-stream");
            //设置下载的文件的名称-该方式已解决中文乱码问题
            response.setHeader("Content-Disposition","attachment;filename=" +  new String( filename.getBytes("gb2312"), "ISO8859-1" ));
            is = new FileInputStream(file);
            bs =new BufferedInputStream(is);
            os = response.getOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while((len = bs.read(buffer)) != -1){
                os.write(buffer,0,len);
            }
        }catch(IOException ex){
            ex.printStackTrace();
        }finally {
            try{
                if(is != null){
                    is.close();
                }
                if( bs != null ){
                    bs.close();
                }
                if( os != null){
                    os.flush();
                    os.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    
    }
}
